﻿/* dk2Repository - Copyright (C) 2011 Daniel Kuczewski http://www.dk2.de/

 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */

using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Threading;
using dk2Repository.Interfaces;

namespace dk2Repository
{
    public sealed class Columns : IEnumerable<Columns.Column>
    {
        public sealed class Column
        {
            public DbType DbType { get; private set; }
            public PropertyInfo PropertyInfo { get; private set; }
            public string Alias { get; private set; }
            public string ColumnName { get; private set; }
            public string Property { get; private set; }
            public bool IsPk { get; private set; }
            public bool IsAutoInc { get; private set; }
            public bool IsGuid { get; private set; }
            public bool IsNullable { get; private set; }
            public uint Length { get; private set; }
            public IConverter Converter { get; private set; }

    
            public Column(PropertyInfo propertyInfo, ColumnAttribute ca)
            {
                ColumnName = !string.IsNullOrEmpty(ca.Column) ? ca.Column : propertyInfo.Name;
                Property = propertyInfo.Name;
                PropertyInfo = propertyInfo;
                Length = ca.Length;

                if (!ca.Nullable.HasValue)
                    IsNullable = PropertyInfo.PropertyType.IsGenericType && PropertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
                else
                    IsNullable = ca.Nullable.Value;

                var mca = (ConverterAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ConverterAttribute));

                IConverter converter = null;
                if (mca != null)
                {
                    converter = mca.GetConverter();
                    if(converter==null)
                        throw new MappingException("Unsupported converter: " + mca.ConverterType.Name);
                    
                }

                var dbtype = ca.Type;

                GetDbTypeAndConverter(propertyInfo.PropertyType, ref dbtype, ref converter);

                converter = converter ?? new DefaultConverter(propertyInfo.PropertyType);

                if (!dbtype.HasValue)
                    throw new MappingException(propertyInfo.Name +
                                               " uses unsupported Type. Manually set DbType and use converter");


                var pka = (PrimaryKeyAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(PrimaryKeyAttribute));

                if (pka != null)
                {
                    IsPk = true;

                    if (PropertyInfo.PropertyType.Equals(typeof(Guid)))
                        IsGuid = true;

                    IsAutoInc = pka.AutoInc;
                }



                DbType = dbtype.Value;
                Converter = converter;
                
            }


            public void SetValue(object item, object value)
            {
                PropertyInfo.SetValue(item, value, null);
            }

            public object GetValue(object item)
            {
                return PropertyInfo.GetValue(item, null);
            }

            public void SetDbValue(object item, object value)
            {
                SetValue(item, Converter.ConvertToEntityValue(value));
            }

            public object GetDbValue(object item)
            {
                return Converter.ConvertToDbValue(GetValue(item));
            }

            private static void GetDbTypeAndConverter(Type type, ref DbType? dbType, ref IConverter converter)
            {
                if (dbType.HasValue && converter != null)
                    return;

                if (type.IsEnum)
                {
                    converter = converter ?? new EnumConverter(type);
                    dbType = dbType ?? DbType.Int64;
                    return;
                }

                if (type == typeof(short) || type == typeof(short?))
                {
                    dbType = dbType ?? DbType.Int16;
                    return;
                }
                if (type == typeof(int) || type == typeof(int?))
                {
                    dbType = dbType ?? DbType.Int32;
                    return;
                }
                if (type == typeof(long) || type == typeof(long?))
                {
                    dbType = dbType ?? DbType.Int64;
                    return;
                }
                if (type == typeof(ushort) || type == typeof(ushort?))
                {
                    dbType = dbType ?? DbType.UInt16;
                    return;
                }
                if (type == typeof(ulong) || type == typeof(ulong?))
                {
                    dbType = dbType ?? DbType.UInt64;
                    return;
                }
                if (type == typeof(uint) || type == typeof(uint?))
                {
                    dbType = dbType ?? DbType.UInt32;
                    return;
                }
                if (type == typeof(float) || type == typeof(float?))
                {
                    dbType = dbType ?? DbType.Single;
                    return;
                }
                if (type == typeof(double) || type == typeof(double?))
                {
                    dbType = dbType ?? DbType.Double;
                    return;
                }
                if (type == typeof(bool) || type == typeof(bool?))
                {
                    dbType = dbType ?? DbType.Boolean;
                    return;
                }
                if (type == typeof(DateTime) || type == typeof(DateTime?))
                {
                    dbType = dbType ?? DbType.DateTime;
                    return;
                }
                if (type == typeof(string))
                {
                    dbType = dbType ?? DbType.String;
                    return;
                }
                if (type == typeof(char[]))
                {
                    dbType = dbType ?? DbType.StringFixedLength;
                    return;
                }


            }
        }

        private readonly Dictionary<string, Column> _namedColumns;
        private readonly List<Column> _columns;
        private readonly List<Column> _columnsNoPk;
        private readonly List<Column> _columnsPk;

        public int Count
        {
            get { return _columns.Count; }
        }

        public Column[] PrimaryKeys
        {
            get { return _columnsPk.ToArray(); }
        }

        public Column[] WithoutPrimaryKey
        {
            get { return _columnsNoPk.ToArray(); }
        }

        public Columns()
        {
            _namedColumns = new Dictionary<string, Column>();
            _columnsNoPk = new List<Column>();
            _columns = new List<Column>();
            _columnsPk = new List<Column>();
        }

        internal void Add(Column column)
        {
            _namedColumns.Add(column.ColumnName, column);
            _columns.Add(column);

            if (column.IsPk)
                _columnsPk.Add(column);
            else
                _columnsNoPk.Add(column);

        }

        public bool Contains(string column)
        {
            return _namedColumns.ContainsKey(column);
        }


        public Column this[string columnname]
        {
            get
            {
                return _namedColumns.ContainsKey(columnname) ? _namedColumns[columnname] : null;
            }
        }

        public IEnumerator<Column> GetEnumerator()
        {
            return _columns.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();

        }
    }
}